#ifndef ENGINE_CPP
#define ENGINE_CPP
#include <GL\glut.h>
#include <math.h>
#include <fstream>
#include "engine.h"
using namespace std;

Decart::Decart ()
{
	this->x = 0;
	this->y = 0;
}

Decart::Decart (double x, double y)
{
	this->x = x;
	this->y = y;
}

Decart::Decart (Polar place)
{
	this->x = cos (place.angle) * place.radius;
	this->y = sin (place.angle) * place.radius;
}

Decart::~Decart ()
{

}

double Decart::X ()
{
	return (this->x - Tr.x) * Zoom;
}

double Decart::Y ()
{
	return (this->y - Tr.y) * Zoom;
}

Decart Decart::operator - (const Decart &place)
{
	return Decart (this->x - place.x, this->y - place.y);
}

Decart Decart::operator + (const Decart &place)
{
	return Decart (this->x + place.x, this->y + place.y);
}

Decart Decart::operator * (const double Coeficient)
{
	return Decart (this->x * Coeficient, this->y * Coeficient);
}

Decart Decart::operator / (const double Coeficient)
{
	return Decart (this->x / Coeficient, this->y / Coeficient);
}

Decart &Decart::operator = (const Polar &place)
{
	this->x = cos (place.angle) * place.radius;
	this->y = sin (place.angle) * place.radius;
	return *this;
}

Decart &Decart::operator += (const Decart &place)
{
	this->x += place.x;
	this->y += place.y;
	return *this;
}

double Decart::Distance () const
{
	return sqrt (pow (this->x, 2) + pow (this->y, 2));
}

double Decart::Distance (Decart place) const
{
	return sqrt (pow (this->x - place.x, 2) + pow (this->y - place.y, 2));
}

double Decart::Angle () const
{
 	if (asin (this->y / this->Distance ()) > 0)
 		return acos (this->x / this->Distance ());
 	else
 		return 2 * PI - acos (this->x / this->Distance ());
}

void Decart::SetRadius (double radius)
{
	this->x *= radius / this->Distance ();
}

void Decart::CutDown (double radius)
{
	if (this->Distance () > radius)
	{
		this->x *= radius / this->Distance ();
		this->y *= radius / this->Distance ();
	}
}

Polar::Polar ()
{
	this->angle = 0;
	this->radius = 0;
}

Polar::Polar (double angle, double radius)
{
	this->angle = angle;
	this->radius = radius;
}

Polar::Polar (Decart place)
{
	this->angle = acos (place.x / place.Distance ());
	this->radius = place.Distance ();
}

Polar::~Polar ()
{

}

Polar &Polar::operator = (const Decart &place)
{
	if (asin (place.y / place.Distance ()) > 0)
		this->angle = acos (place.x / place.Distance ());
	else this->angle = 2 * PI - acos (place.x / place.Distance ());
	this->radius = place.Distance ();
	return *this;
}

void Polar::CutDown (double radius)
{
	if (this->radius > radius) this->radius = radius;
}

void Polar::CutAngle ()
{
	while (this->angle >= 0 && this->angle < 2 * PI)
	{
		if (this->angle >= 2 * PI) this->angle -= 2 * PI;
		else this->angle += 2 * PI;
	}
}

ObjectInfo::ObjectInfo ()
{
	this->weight = 0;
	this->setup = false;
	this->exist = false;
	this->type = NotExist;
	this->place = Decart (0, 0);
}

ObjectInfo::~ObjectInfo ()
{

}


double ObjectInfo::GetSize ()
{
	switch (this->type)
	{
	case Planet:
		return 14 + this->weight * 18 / MaxObjectWeight;
	break;
	case Star:
		return 40 + this->weight * 20 / MaxObjectWeight;
	break;
	default:
		return 6 + this->weight * 8 / MaxObjectWeight;
	break;
	}
}

double ObjectInfo::FullWeight ()
{
	return this->type * this->weight;
}

void ObjectInfo::Create (Decart place)
{
	this->exist = true;
	this->setup = false;
	this->place = place;
	this->weight = 30;
	this->type = Comet;
	this->tail = new Decart[TailLenth];
	for (int i = 0; i < TailLenth; i++)
		this->tail[i] = this->place;
}

void ObjectInfo::Delete ()
{
	this->exist = false;
	this->setup = false;
	this->place = Decart (0, 0);
	this->speed = Decart (0, 0);
	this->type = NotExist;
	this->weight = 0;
}

// void ObjectInfo::CountSpeed ()
// {
// 	for (int i = 0; i < ObjectAmount; i++)
// 		if (Object[i].setup && Object[i].exist)
// 			this->speed.y = this->speed.y - 1 / double (CountingAcuracy);
// }
void ObjectInfo::CountSpeed ()
{
	Polar Accel;
	for (int i = 0; i < ObjectAmount; i++)
	if (Object[i].setup && Object[i].exist && this != &Object[i])
	{
		Accel.radius = Object[i].FullWeight () * Object[i].FullWeight ()
			* SimulationSpeed / (CountingAcuracy * 10 * (Object[i].place
				- this->place).Distance () * (this->FullWeight ()
					+ Object[i].FullWeight ()));
		if (Accel.radius > MaxObjectAccel) Accel.radius = MaxObjectAccel;
		Accel.angle = (Object[i].place - this->place).Angle ();
		this->speed = this->speed + Decart (Accel);
	}
}

ButtonInfo::ButtonInfo(int Top, int Bottom, int LSide, int RSide)
{
	this->Top = Top;
	this->Bottom = Bottom;
	this->LSide = LSide;
	this->RSide = RSide;
}

double getMinDistance (int a, int b)
{
	return Object[a].GetSize () + Object[b].GetSize ();
}

void UpdateTail (int N)
{
	for (int i = TailLenth - 1; i > 0; i--)
		Object[N].tail[i] = Object[N].tail[i - 1];
	Object[N].tail[0] = Object[N].place;
}

void Merge (const int a, const int b)
{
	ObjectType NewType = Object[a].type;
	if (Object[b].type > Object[a].type) NewType = Object[b].type;
	double NewWeight = (Object[a].FullWeight () + Object[b].FullWeight ()) / NewType;
	Object[a].place = Object[a].place + (Object[b].place - Object[a].place)
		* Object[b].FullWeight () 
		/ (Object[a].FullWeight () + Object[b].FullWeight ());
	Object[a].speed = (Object[a].speed * Object[a].FullWeight ()
		+ Object[b].speed * Object[b].FullWeight ()) / (NewWeight * NewType);

	Object[a].weight = NewWeight;
	if (Object[a].weight > MaxObjectWeight) Object[a].weight = MaxObjectWeight;
	Object[a].type = NewType;
	Object[a].speed = Object[a].speed * (1 - CollisionResistance);
	Object[b].Delete ();
}

void Bounce (int a, int b)
{
	Decart CommonRadius;
	Polar Buffer;
	while (Object[a].place.Distance (Object[b].place) < getMinDistance (a, b))
	{
		Object[a].place = Object[a].place - Object[a].speed / 100;
		Object[b].place = Object[b].place - Object[b].speed / 100;
	}
	CommonRadius = Object[b].place - Object[a].place;
	Buffer.angle = CommonRadius.Angle ();
	Buffer.radius = abs ((Object[a].speed - Object[b].speed).Distance ()
		* cos (CommonRadius.Angle () - (Object[b].speed
			- Object[a].speed).Angle ()));
	Object[a].speed = Object[a].speed - Decart (Buffer) * 2 * (Object[b].weight
		* Object[b].type / (Object[a].weight * Object[a].type + Object[b].weight
			* Object[b].type));
	Object[b].speed = Object[b].speed + Decart (Buffer) * 2 * (Object[a].weight
		* Object[a].type / (Object[a].weight * Object[a].type + Object[b].weight
			* Object[b].type));
	Object[a].speed = Object[a].speed * (1 - CollisionResistance / 2);
	Object[b].speed = Object[b].speed * (1 - CollisionResistance / 2);
}

void Collide (const int &a, const int &b)
{
	Merge (a, b);
	// Bounce (a, b);
}

void CheckWindowPosition()
{
	if (Tr.y > - Win.H / 2 / Zoom + Win.H * SBS + Win.H / 40 / Zoom)
		Tr.y = - Win.H / 2 / Zoom + Win.H * SBS + Win.H / 40 / Zoom;
	if (Tr.y < Win.H / 2 / Zoom - Win.H * SBS - Win.H / 40 / Zoom)
		Tr.y = Win.H / 2 / Zoom - Win.H * SBS - Win.H / 40 / Zoom;
	if (Tr.x < Win.W / 2 / Zoom - Win.W * SBS - Win.W / 40 / Zoom)
		Tr.x = Win.W / 2 / Zoom - Win.W * SBS - Win.W / 40 / Zoom;
	if (Tr.x > - Win.W / 2 / Zoom + Win.W * SBS + Win.W / 40 / Zoom)
		Tr.x = - Win.W / 2 / Zoom + Win.W * SBS + Win.W / 40 / Zoom;
}

void ClearScreen (void)
{
	for (int i = 0; i < ObjectAmount; i++) Object[i].Delete ();
}

void clr (char *Buf)
{
	char ClearBuffer[10];
	Buf = ClearBuffer;
}

// Actions, that are performed when user press the mouse button
void MouseAction (MouseButtonType Button)
{
	switch (Button)
	{
		case ToolbarRemove:
			Object[EditObject].Delete ();
			EditObject = NoEditObject;
		break;
		case ToolbarAccept:
			Object[EditObject].setup = true;
			EditObject = NoEditObject;
		break;
		case ToolbarWeight:
			Object[EditObject].weight = Mouse.x - 100;
		break;
		case ToolbarNextType:
			switch(Object[EditObject].type)
			{
			case Comet:
				Object[EditObject].type = Meteor;
				delete[] Object[EditObject].tail;
			break;
			case Meteor:
				Object[EditObject].type = Planet;
			break;
			case Planet:
				Object[EditObject].type = Star;
			break;
			case Star:
				Object[EditObject].type = Comet;
				Object[EditObject].tail = new Decart[TailLenth];
				for (int i = 0; i < TailLenth; i++)
					Object[EditObject].tail[i] = Object[EditObject].place;
			break;
			default:
			break;
			}
		break;
		case ToolbarPrevType:
			switch(Object[EditObject].type)
			{
			case Comet:
				Object[EditObject].type = Star;
				delete[] Object[EditObject].tail;
			break;
			case Meteor:
				Object[EditObject].type = Comet;
				Object[EditObject].tail = new Decart[TailLenth];
				for (int i = 0; i < TailLenth; i++)
					Object[EditObject].tail[i] = Object[EditObject].place;
			break;
			case Planet:
				Object[EditObject].type = Meteor;
			break;
			case Star:
				Object[EditObject].type = Planet;
			break;
			default:
			break;
			}
		break;
		case TooClose:
			Alert = TooCloseToOther;
			AlertDelay = AlertDuration;
		break;
		case NoKey:
			// Alert = ImpossibleLocation;
			// AlertDelay = AlertDuration;
		break;
		case EmptyField:
			SpeedSetup = true;
			if (EditObject != NoEditObject)
			{
				Object[EditObject].place = Mouse / Zoom + Tr;
				for (int i = 0; i < TailLenth; i++)
					Object[EditObject].tail[i] = Object[EditObject].place;
				Object[EditObject].speed = Decart (0, 0);
			}
			else
			{
				for (int i = 0; i < ObjectAmount; i++)
					if (!Object[i].exist && EditObject == NoEditObject)
						EditObject = i;
				if (EditObject == NoEditObject)
				{
					Alert = MaxObjectAmount;
					AlertDelay = AlertDuration;
				}
				else
				{
					Object[EditObject].Create (Mouse / Zoom + Tr);
				}
			};
		break;
		case OtherObject:
			for (int i = 0; i < ObjectAmount; i++)
				if (Object[i].exist && (Object[i].place.Distance (Mouse / Zoom + Tr)
					< Object[i].GetSize ()
						|| Object[i].place.Distance (Mouse / Zoom + Tr) * Zoom < 5)) EditObject = i;
		break;
		case Minimap:
			Tr.x = (Mouse.x - 9 * Win.W / 20) * 400;
			Tr.y = (Mouse.y + 9 * Win.H / 20) * 400;
			CheckWindowPosition();
		break;
		default:

		break;
	}
}

MouseButtonType CatchButton ()
{
	Size W = Win; // To make variable names shorter
	Decart &M = Mouse;
	switch (CurrentPressedButton)
	{
	case GLUT_LEFT_BUTTON:
		if (M.x > 4 * W.W / 10 && M.y < - 4 * W.H / 10)
		{
			WinPosSetup = true;
			return Minimap;
		}
		if (EditObject != NoEditObject) // If toolbar is shown
		{
			if (M.x > - 262 && M.x < 262 && M.y > - W.H / 2 + 8 && M.y < - W.H / 2 + 52)
			{
				if (M.x > - 260 && M.x < - 221 && M.y > - W.H / 2 + 10 && M.y < - W.H / 2 + 50)
					return ToolbarRemove;
				if (M.x > 221 && M.x < 260 && M.y > - W.H / 2 + 10 && M.y < - W.H / 2 + 50)
					return ToolbarAccept;
				if (M.x > 109 && M.x < 201 && M.y > - W.H / 2 + 2 && M.y < - W.H / 2 + 28)
				{
					WeightSetup = true;
					return ToolbarWeight;
				}
				if (M.x > - 150 && M.x < - 123 && M.y > - W.H / 2 + 2 && M.y < - W.H / 2 + 28)
					return ToolbarPrevType;
				if (M.x > - 123 && M.x < 3 && M.y > - W.H / 2 + 2 && M.y < - W.H / 2 + 28)
					return ToolbarNextType;
				return NoKey;
			}

			for (int i = 0; i < ObjectAmount; i++)
				if (Object[i].setup && Object[i].place.Distance (M / Zoom + Tr)
					< Object[i].GetSize () + MaxObjectSize
						&& EditObject != i) return TooClose;
			
			return EmptyField;
		}
		else
		{
			if (M.y < W.H / 2 - 20) // 20 - menu height
			{
				for (int i = 0; i < ObjectAmount; i++)
					if (Object[i].setup && (Object[i].place.Distance (M / Zoom + Tr) < Object[i].GetSize ()
						|| Object[i].place.Distance (M / Zoom + Tr) * Zoom < 5) && EditObject != i)
							return OtherObject;
				for (int i = 0; i < ObjectAmount; i++)
					if (Object[i].setup && Object[i].place.Distance (M / Zoom + Tr) < Object[i].GetSize () + MaxObjectSize
						&& EditObject != i) return TooClose;
				return EmptyField;
			}
		}
	break;
	case GLUT_RIGHT_BUTTON:
		if (EditObject == NoEditObject)
		{
			for (int i = 0; i < ObjectAmount; i++)
				if (Object[i].place.Distance (M / Zoom + Tr) < Object[i].GetSize ()
					|| Object[i].place.Distance (M / Zoom + Tr) * Zoom < 5)
				{
					Object[i].Delete ();
					if (i == EditObject)
						EditObject = NoEditObject;	
				};	
		}
		else
		{
			Object[EditObject].Delete ();
			EditObject = NoEditObject;
		}
	break;
	}
	return NoKey;
}
void SandBox (double Size)
{
	for (int i = 0; i < ObjectAmount; i++) if (Object[i].exist)
	{
		if (Object[i].place.x - Object[i].GetSize() < - Win.W * SBS)
		{
			Object[i].speed.x *= - 1;
			Object[i].place.x = - Win.W * SBS + Object[i].GetSize();
		}
		if (Object[i].place.x + Object[i].GetSize() > Win.W * SBS)
		{
			Object[i].speed.x *= - 1;
			Object[i].place.x = Win.W * SBS - Object[i].GetSize();
		}
		if (Object[i].place.y - Object[i].GetSize() < - Win.H * SBS)
		{
			Object[i].speed.y *= - 1;
			Object[i].place.y = - Win.H * SBS + Object[i].GetSize();
		}
		if (Object[i].place.y + Object[i].GetSize() > Win.H * SBS)
		{
			Object[i].speed.y *= - 1;
			Object[i].place.y = Win.H * SBS - Object[i].GetSize();
		}	
	}
}
#endif